#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
#include "handle_backend_connection.h"
#include "http_server.h"
#include "headers.h"
#include "write_buffer.h"
#include "read_buffer.h"
#include "send_error_page.h"
#include "end_of_request.h"
#include "parse_chunked_body.h"
#include "log.h"
#include "methods.h"
#include "version.h"

#define RESPONSE_ENTITY_TOO_LARGE (10 * 1024)

#define POST_RECV_BUFFER          4096

#define IS_WHITE_SPACE(x)         ((x == ' ') || (x == '\t'))
#define IS_ALPHA(x)               (((x >= 'A') && (x <= 'Z')) || ((x >= 'a') && (x <= 'z')))
#define IS_DIGIT(x)               ((x >= '0') && (x <= '9'))
#define IS_MARK(x)                ((x == '-') || (x ==  '_') || (x == '.') || (x == '!') || (x == '~') || (x == '*') || (x == '\'') || (x == '(') || (x == ')'))

static int read_response_header (http_connection_t *backend_connection, http_connection_t *client_connection);
static int prepare_request_to_backend (http_connection_t *backend_connection, http_connection_t *client_connection);
static int prepare_response_to_client (http_connection_t *backend_connection, http_connection_t *client_connection);

int handle_backend_connection (http_connection_t *backend_connection, int events)
{
	http_connection_t *client_connection;
	backend_list_t *backend_list;
	backend_t *backend;
	buffer_t *buffer;
	int optval;
	int error;
	socklen_t optlen;
	size_t body_size;
	int ret;

	client_connection = &(http_server.http_connections[backend_connection->fd]);

	if ((events & EVENT_ERR) || (events & EVENT_HUP)) {
		if (backend_connection->http_connection_state == CONNECTING_TO_BACKEND) {
			/* Did we manage to connect to the backend/HTTP server? */
			optlen = sizeof (optval);
			if ((getsockopt (backend_connection->sd, SOL_SOCKET, SO_ERROR, &error, &optlen) < 0) || (error)) {
				/* Connection failed. */
				if (client_connection->proxy_connection) {
					/* 502 Bad Gateway. */
					if (http_server.log_fd != -1) {
						log_error ("Couldn't connect to HTTP server: [%.*s].", client_connection->host.used, client_connection->host.data);
					}

					if (send_error_page (client_connection, 502, NULL) < 0) {
						http_server_remove_connection (&http_server, client_connection->sd);
					}
				} else {
					/* Mark backend as unavailable and try with next one (if possible). */
					backend_list = (backend_list_t *) client_connection->backend_list;
					backend = &(backend_list->backends[client_connection->backend]);
					if (http_server.log_fd != -1) {
						log_error ("Couldn't connect to backend: [%.*s].", backend->host.used, backend->host.data);
					}

					backend->weight = -1;
					client_connection->fd = -1;

					/* If this was the last backend... */
					if (client_connection->connection_attempts == backend_list->used) {
						/* No more backends. */
						/* 502 Bad Gateway. */
						if (send_error_page (client_connection, 502, NULL) < 0) {
							http_server_remove_connection (&http_server, client_connection->sd);
						}
					} else if (backend_list_connect (backend_list, client_connection) < 0) {
						/* No more backends. */
						/* 502 Bad Gateway. */
						if (send_error_page (client_connection, 502, NULL) < 0) {
							http_server_remove_connection (&http_server, client_connection->sd);
						}
					}
				}
			}
		} else {
			client_connection->fd = -1;
			http_server_remove_connection (&http_server, client_connection->sd);
		}

		backend_connection->fd = -1;
		return -1;
	}

	if (events & EVENT_OUT) {
		switch (backend_connection->http_connection_state) {
			case CONNECTING_TO_BACKEND:
				if (prepare_request_to_backend (backend_connection, client_connection) < 0) {
					client_connection->fd = -1;
					http_server_remove_connection (&http_server, client_connection->sd);

					backend_connection->fd = -1;
					return -1;
				}

				/* Send request to backend. */
				backend_connection->http_connection_state = SENDING_REQUEST_TO_BACKEND;

#ifdef HAVE_TCP_CORK
				optval = 1;
				setsockopt (backend_connection->sd, IPPROTO_TCP, TCP_CORK, &optval, sizeof (int));
#endif /* HAVE_TCP_CORK */

				buffer = &backend_connection->buffer;
				if ((ret = write_buffer (backend_connection, buffer)) < 0) {
					if (send_error_page (client_connection, 502, NULL) < 0) {
						client_connection->fd = -1;
						http_server_remove_connection (&http_server, client_connection->sd);
					}

					backend_connection->fd = -1;
					return -1;
				} else if (ret == 0) {
					return 0;
				}

				if ((client_connection->http_method != GET_METHOD) && (client_connection->http_method != HEAD_METHOD) && (client_connection->http_method != DELETE_METHOD) && ((client_connection->content_length != -1) || (client_connection->chunked))) {
					body_size = client_connection->buffer.used - client_connection->header_offsets[0][1];
					if (client_connection->chunked) {
						client_connection->offset = client_connection->header_offsets[0][1];

						client_connection->request_state = 0;

						client_connection->nCRLFs = 0;

						if (body_size) {
							if ((ret = parse_chunked_body (client_connection)) < 0) {
								/* 400 Bad Request. */
								if (send_error_page (client_connection, 400, NULL) < 0) {
									/* Remove client connection. */
									client_connection->fd = -1;
									http_server_remove_connection (&http_server, client_connection->sd);
								}

								backend_connection->fd = -1;
								return -1;
							} else if (ret == 1) {
#ifdef HAVE_TCP_CORK
								optval = 0;
								setsockopt (backend_connection->sd, IPPROTO_TCP, TCP_CORK, &optval, sizeof (int));
#endif /* HAVE_TCP_CORK */

								buffer_reset (buffer);

								if (http_server.fn_prepare_for_reading (backend_connection) < 0) {
									client_connection->fd = -1;
									http_server_remove_connection (&http_server, client_connection->sd);

									backend_connection->fd = -1;
									return -1;
								}

								backend_connection->http_connection_state = READING_RESPONSE_HEADER_FROM_BACKEND;
								backend_connection->offset = 0;
								return 0;
							}
						}
					} else {
						/* If we have already sent the whole body... */
						if (body_size >= client_connection->content_length) {
#ifdef HAVE_TCP_CORK
							optval = 0;
							setsockopt (backend_connection->sd, IPPROTO_TCP, TCP_CORK, &optval, sizeof (int));
#endif /* HAVE_TCP_CORK */

							buffer_reset (buffer);

							if (http_server.fn_prepare_for_reading (backend_connection) < 0) {
								client_connection->fd = -1;
								http_server_remove_connection (&http_server, client_connection->sd);

								backend_connection->fd = -1;
								return -1;
							}

							backend_connection->http_connection_state = READING_RESPONSE_HEADER_FROM_BACKEND;
							backend_connection->offset = 0;
							return 0;
						}

						client_connection->content_length -= body_size;
					}

					if (http_server.fn_prepare_for_reading (client_connection) < 0) {
						client_connection->fd = -1;
						http_server_remove_connection (&http_server, client_connection->sd);

						backend_connection->fd = -1;
						return -1;
					}

					if (http_server.fn_remove_notifications (backend_connection) < 0) {
						client_connection->fd = -1;
						http_server_remove_connection (&http_server, client_connection->sd);

						backend_connection->fd = -1;
						return -1;
					}

					buffer_reset (&client_connection->buffer);
					if (buffer_allocate (&client_connection->buffer, POST_RECV_BUFFER) < 0) {
						client_connection->fd = -1;
						http_server_remove_connection (&http_server, client_connection->sd);

						backend_connection->fd = -1;
						return -1;
					}

					client_connection->http_connection_state = READING_POST_FROM_CLIENT;
					client_connection->offset = 0;

					backend_connection->http_connection_state = SENDING_POST_TO_BACKEND;
				} else {
#ifdef HAVE_TCP_CORK
					optval = 0;
					setsockopt (backend_connection->sd, IPPROTO_TCP, TCP_CORK, &optval, sizeof (int));
#endif /* HAVE_TCP_CORK */

					buffer_reset (buffer);

					if (http_server.fn_prepare_for_reading (backend_connection) < 0) {
						client_connection->fd = -1;
						http_server_remove_connection (&http_server, client_connection->sd);

						backend_connection->fd = -1;
						return -1;
					}

					backend_connection->http_connection_state = READING_RESPONSE_HEADER_FROM_BACKEND;
					backend_connection->offset = 0;
				}

				return 0;
			case SENDING_REQUEST_TO_BACKEND:
				/* Send request to backend. */
				buffer = &backend_connection->buffer;
				if ((ret = write_buffer (backend_connection, buffer)) < 0) {
					if (send_error_page (client_connection, 502, NULL) < 0) {
						client_connection->fd = -1;
						http_server_remove_connection (&http_server, client_connection->sd);
					}

					backend_connection->fd = -1;
					return -1;
				} else if (ret == 0) {
					return 0;
				}

				if ((client_connection->http_method != GET_METHOD) && (client_connection->http_method != HEAD_METHOD) && (client_connection->http_method != DELETE_METHOD) && ((client_connection->content_length != -1) || (client_connection->chunked))) {
					body_size = client_connection->buffer.used - client_connection->header_offsets[0][1];
					if (client_connection->chunked) {
						client_connection->offset = client_connection->header_offsets[0][1];

						client_connection->request_state = 0;

						client_connection->nCRLFs = 0;

						if (body_size) {
							if ((ret = parse_chunked_body (client_connection)) < 0) {
								/* 400 Bad Request. */
								if (send_error_page (client_connection, 400, NULL) < 0) {
									/* Remove client connection. */
									client_connection->fd = -1;
									http_server_remove_connection (&http_server, client_connection->sd);
								}

								backend_connection->fd = -1;
								return -1;
							} else if (ret == 1) {
#ifdef HAVE_TCP_CORK
								optval = 0;
								setsockopt (backend_connection->sd, IPPROTO_TCP, TCP_CORK, &optval, sizeof (int));
#endif /* HAVE_TCP_CORK */

								buffer_reset (buffer);

								if (http_server.fn_prepare_for_reading (backend_connection) < 0) {
									client_connection->fd = -1;
									http_server_remove_connection (&http_server, client_connection->sd);

									backend_connection->fd = -1;
									return -1;
								}

								backend_connection->http_connection_state = READING_RESPONSE_HEADER_FROM_BACKEND;
								backend_connection->offset = 0;
								return 0;
							}
						}
					} else {
						/* If we have already sent the whole body... */
						if (body_size >= client_connection->content_length) {
#ifdef HAVE_TCP_CORK
							optval = 0;
							setsockopt (backend_connection->sd, IPPROTO_TCP, TCP_CORK, &optval, sizeof (int));
#endif /* HAVE_TCP_CORK */

							buffer_reset (buffer);

							if (http_server.fn_prepare_for_reading (backend_connection) < 0) {
								client_connection->fd = -1;
								http_server_remove_connection (&http_server, client_connection->sd);

								backend_connection->fd = -1;
								return -1;
							}

							backend_connection->http_connection_state = READING_RESPONSE_HEADER_FROM_BACKEND;
							backend_connection->offset = 0;
							return 0;
						}

						client_connection->content_length -= body_size;
					}

					if (http_server.fn_prepare_for_reading (client_connection) < 0) {
						client_connection->fd = -1;
						http_server_remove_connection (&http_server, client_connection->sd);

						backend_connection->fd = -1;
						return -1;
					}

					if (http_server.fn_remove_notifications (backend_connection) < 0) {
						client_connection->fd = -1;
						http_server_remove_connection (&http_server, client_connection->sd);

						backend_connection->fd = -1;
						return -1;
					}

					buffer_reset (&client_connection->buffer);
					if (buffer_allocate (&client_connection->buffer, POST_RECV_BUFFER) < 0) {
						client_connection->fd = -1;
						http_server_remove_connection (&http_server, client_connection->sd);

						backend_connection->fd = -1;
						return -1;
					}

					client_connection->http_connection_state = READING_POST_FROM_CLIENT;
					client_connection->offset = 0;

					backend_connection->http_connection_state = SENDING_POST_TO_BACKEND;
				} else {
#ifdef HAVE_TCP_CORK
					optval = 0;
					setsockopt (backend_connection->sd, IPPROTO_TCP, TCP_CORK, &optval, sizeof (int));
#endif /* HAVE_TCP_CORK */

					buffer_reset (buffer);

					if (http_server.fn_prepare_for_reading (backend_connection) < 0) {
						client_connection->fd = -1;
						http_server_remove_connection (&http_server, client_connection->sd);

						backend_connection->fd = -1;
						return -1;
					}

					backend_connection->http_connection_state = READING_RESPONSE_HEADER_FROM_BACKEND;
					backend_connection->offset = 0;
				}

				return 0;
			case SENDING_POST_TO_BACKEND:
				buffer = &client_connection->buffer;
				if ((ret = write_buffer (backend_connection, buffer)) < 0) {
					if (send_error_page (client_connection, 502, NULL) < 0) {
						client_connection->fd = -1;
						http_server_remove_connection (&http_server, client_connection->sd);
					}

					backend_connection->fd = -1;
					return -1;
				} else if (ret == 0) {
					return 0;
				}

				if (client_connection->chunked) {
					client_connection->offset = 0;

					if ((ret = parse_chunked_body (client_connection)) < 0) {
						/* 400 Bad Request. */
						if (send_error_page (client_connection, 400, NULL) < 0) {
							/* Remove client connection. */
							client_connection->fd = -1;
							http_server_remove_connection (&http_server, client_connection->sd);
						}

						backend_connection->fd = -1;
						return -1;
					} else if (ret == 1) {
#ifdef HAVE_TCP_CORK
						optval = 0;
						setsockopt (backend_connection->sd, IPPROTO_TCP, TCP_CORK, &optval, sizeof (int));
#endif /* HAVE_TCP_CORK */

						buffer_reset (&backend_connection->buffer);

						if (http_server.fn_prepare_for_reading (backend_connection) < 0) {
							client_connection->fd = -1;
							http_server_remove_connection (&http_server, client_connection->sd);

							backend_connection->fd = -1;
							return -1;
						}

						backend_connection->http_connection_state = READING_RESPONSE_HEADER_FROM_BACKEND;
						backend_connection->offset = 0;
						return 0;
					}
				} else {
					/* If we have already sent the whole body... */
					if (buffer->used >= client_connection->content_length) {
#ifdef HAVE_TCP_CORK
						optval = 0;
						setsockopt (backend_connection->sd, IPPROTO_TCP, TCP_CORK, &optval, sizeof (int));
#endif /* HAVE_TCP_CORK */

						buffer_reset (&backend_connection->buffer);

						if (http_server.fn_prepare_for_reading (backend_connection) < 0) {
							client_connection->fd = -1;
							http_server_remove_connection (&http_server, client_connection->sd);

							backend_connection->fd = -1;
							return -1;
						}

						backend_connection->http_connection_state = READING_RESPONSE_HEADER_FROM_BACKEND;
						backend_connection->offset = 0;
						return 0;
					}

					client_connection->content_length -= buffer->used;
				}

				if (http_server.fn_prepare_for_reading (client_connection) < 0) {
					client_connection->fd = -1;
					http_server_remove_connection (&http_server, client_connection->sd);

					backend_connection->fd = -1;
					return -1;
				}

				if (http_server.fn_remove_notifications (backend_connection) < 0) {
					client_connection->fd = -1;
					http_server_remove_connection (&http_server, client_connection->sd);

					backend_connection->fd = -1;
					return -1;
				}

				buffer_reset (buffer);

				client_connection->offset = 0;
				return 0;
			default:
				client_connection->fd = -1;
				http_server_remove_connection (&http_server, client_connection->sd);

				backend_connection->fd = -1;
				return -1;
		}
	} else if (events & EVENT_IN) {
		switch (backend_connection->http_connection_state) {
			case READING_RESPONSE_HEADER_FROM_BACKEND:
				if ((ret = read_response_header (backend_connection, client_connection)) < 0) {
					if (send_error_page (client_connection, 502, NULL) < 0) {
						client_connection->fd = -1;
						http_server_remove_connection (&http_server, client_connection->sd);
					}

					backend_connection->fd = -1;
					return -1;
				} else if (ret == 0) {
					return 0;
				}

				/* If the response header didn't have the right format... */
				if (ret != 200) {
					/* 502 Bad Gateway. */
					if (send_error_page (client_connection, 502, NULL) < 0) {
						/* Remove client connection. */
						client_connection->fd = -1;
						http_server_remove_connection (&http_server, client_connection->sd);
					}

					backend_connection->fd = -1;
					return -1;
				}

				if ((backend_connection->content_length == -1) && (!backend_connection->chunked) && (client_connection->major_number == 1) && (client_connection->minor_number == 0)) {
					client_connection->keep_alive = 0;
					backend_connection->content_length = 0;
				}

				if (prepare_response_to_client (backend_connection, client_connection) < 0) {
					client_connection->fd = -1;
					http_server_remove_connection (&http_server, client_connection->sd);

					backend_connection->fd = -1;
					return -1;
				}

				if (http_server.fn_prepare_for_writing (client_connection) < 0) {
					client_connection->fd = -1;
					http_server_remove_connection (&http_server, client_connection->sd);

					backend_connection->fd = -1;
					return -1;
				}

				if (client_connection->http_method == HEAD_METHOD) {
					client_connection->fd = -1;
					backend_connection->fd = -1;

					client_connection->http_connection_state = WRITING_RESPONSE_TO_CLIENT;
					client_connection->offset = 0;
					return -1;
				}

				if (http_server.fn_remove_notifications (backend_connection) < 0) {
					client_connection->fd = -1;
					http_server_remove_connection (&http_server, client_connection->sd);

					backend_connection->fd = -1;
					return -1;
				}

#ifdef HAVE_TCP_CORK
				optval = 1;
				setsockopt (client_connection->sd, IPPROTO_TCP, TCP_CORK, &optval, sizeof (int));
#endif /* HAVE_TCP_CORK */

				client_connection->http_connection_state = WRITING_RESPONSE_TO_CLIENT;
				client_connection->offset = 0;
				return 0;
			case READING_RESPONSE_FROM_BACKEND:
				if ((ret = read_buffer (backend_connection)) == 0) {
					return 0;
				}

				if (backend_connection->content_length == -1) {
					buffer = &backend_connection->buffer;
					buffer_reset (&client_connection->buffer);

					if (ret < 0) {
						if (buffer_append_size_bounded_string (&client_connection->buffer, "\r\n0\r\n\r\n", 7) < 0) {
							client_connection->fd = -1;
							http_server_remove_connection (&http_server, client_connection->sd);

							backend_connection->fd = -1;
							return -1;
						}
					} else {
						if ((buffer_format (&client_connection->buffer, "\r\n%x\r\n", buffer->used) < 0) || (buffer_append_size_bounded_string (&client_connection->buffer, buffer->data, buffer->used) < 0)) {
							client_connection->fd = -1;
							http_server_remove_connection (&http_server, client_connection->sd);

							backend_connection->fd = -1;
							return -1;
						}
					}

					if (http_server.fn_prepare_for_writing (client_connection) < 0) {
						client_connection->fd = -1;
						http_server_remove_connection (&http_server, client_connection->sd);

						backend_connection->fd = -1;
						return -1;
					}

					if (ret < 0) {
						client_connection->http_connection_state = WRITING_RESPONSE_TO_CLIENT;
						client_connection->offset = 0;

						client_connection->fd = -1;
						backend_connection->fd = -1;
						return -1;
					}

					if (http_server.fn_remove_notifications (backend_connection) < 0) {
						client_connection->fd = -1;
						http_server_remove_connection (&http_server, client_connection->sd);

						backend_connection->fd = -1;
						return -1;
					}

					client_connection->http_connection_state = WRITING_RESPONSE_TO_CLIENT;
					client_connection->offset = 0;
					return 0;
				}

				if (ret < 0) {
					client_connection->fd = -1;
					if (end_of_request (client_connection) < 0) {
						http_server_remove_connection (&http_server, client_connection->sd);
					}

					backend_connection->fd = -1;
					return -1;
				}

				if (http_server.fn_prepare_for_writing (client_connection) < 0) {
					client_connection->fd = -1;
					http_server_remove_connection (&http_server, client_connection->sd);

					backend_connection->fd = -1;
					return -1;
				}

				if (http_server.fn_remove_notifications (backend_connection) < 0) {
					client_connection->fd = -1;
					http_server_remove_connection (&http_server, client_connection->sd);

					backend_connection->fd = -1;
					return -1;
				}

				client_connection->http_connection_state = WRITING_RESPONSE_TO_CLIENT;
				client_connection->offset = 0;
				return 0;
			default:
				client_connection->fd = -1;
				http_server_remove_connection (&http_server, client_connection->sd);

				backend_connection->fd = -1;
				return -1;
		}
	}

	return -1;
}

int handle_client_connection (http_connection_t *client_connection, int events)
{
	http_connection_t *backend_connection;
	buffer_t *buffer;
	int ret;

	if (client_connection->fd != -1) {
		backend_connection = &(http_server.http_connections[client_connection->fd]);
	} else {
		backend_connection = NULL;
	}

	if ((events & EVENT_ERR) || (events & EVENT_HUP)) {
		if (backend_connection) {
			backend_connection->fd = -1;
			http_server_remove_connection (&http_server, backend_connection->sd);
		}

		client_connection->fd = -1;
		return -1;
	}

	if (events & EVENT_OUT) {
		switch (client_connection->http_connection_state) {
			case WRITING_RESPONSE_TO_CLIENT:
				if ((backend_connection) && (backend_connection->http_connection_state == READING_RESPONSE_FROM_BACKEND) && (backend_connection->content_length != -1)) {
					buffer = &backend_connection->buffer;
				} else {
					buffer = &client_connection->buffer;
				}

				if ((ret = write_buffer (client_connection, buffer)) < 0) {
					if (backend_connection) {
						backend_connection->fd = -1;
						http_server_remove_connection (&http_server, backend_connection->sd);
					}

					client_connection->fd = -1;
					return -1;
				} else if (ret == 0) {
					return 0;
				}

				/* Last chunk? */
				if (!backend_connection) {
					return end_of_request (client_connection);
				}

				if (http_server.fn_prepare_for_reading (backend_connection) < 0) {
					backend_connection->fd = -1;
					http_server_remove_connection (&http_server, backend_connection->sd);

					client_connection->fd = -1;
					return -1;
				}

				if (http_server.fn_remove_notifications (client_connection) < 0) {
					backend_connection->fd = -1;
					http_server_remove_connection (&http_server, backend_connection->sd);

					client_connection->fd = -1;
					return -1;
				}

				buffer_reset (&backend_connection->buffer);

				backend_connection->http_connection_state = READING_RESPONSE_FROM_BACKEND;
				backend_connection->offset = 0;
				return 0;
			default:
				if (backend_connection) {
					backend_connection->fd = -1;
					http_server_remove_connection (&http_server, backend_connection->sd);
				}

				client_connection->fd = -1;
				return -1;
		}
	} else if (events & EVENT_IN) {
		switch (client_connection->http_connection_state) {
			case READING_POST_FROM_CLIENT:
				if ((ret = read_buffer (client_connection)) < 0) {
					backend_connection->fd = -1;
					http_server_remove_connection (&http_server, backend_connection->sd);

					client_connection->fd = -1;
					return -1;
				} else if (ret == 0) {
					return 0;
				}

				if (http_server.fn_prepare_for_writing (backend_connection) < 0) {
					backend_connection->fd = -1;
					http_server_remove_connection (&http_server, backend_connection->sd);

					client_connection->fd = -1;
					return -1;
				}

				if (http_server.fn_remove_notifications (client_connection) < 0) {
					backend_connection->fd = -1;
					http_server_remove_connection (&http_server, backend_connection->sd);

					client_connection->fd = -1;
					return -1;
				}

				backend_connection->offset = 0;
				return 0;
			default:
				if (backend_connection) {
					backend_connection->fd = -1;
					http_server_remove_connection (&http_server, backend_connection->sd);
				}

				client_connection->fd = -1;
				return -1;
		}
	}

	return -1;
}

int read_response_header (http_connection_t *backend_connection, http_connection_t *client_connection)
{
	buffer_t *buffer = &backend_connection->buffer;
	char *data;
	char *ptr, *end;
	off_t offset;
	unsigned char c;
	int ret;
	int status_code;
	size_t len;
	size_t content_length;

	status_code = -1;

	do {
		if (buffer->used >= RESPONSE_ENTITY_TOO_LARGE) {
			if (status_code < 0) {
				return 413; /* Request Entity Too Large. */
			}

			return status_code;
		}

		if (buffer_allocate (buffer, buffer->buffer_increment) < 0) {
			return -1;
		}

		if ((ret = read_buffer (backend_connection)) < 0) {
			return status_code;
		} else if (ret == 0) {
			if (status_code < 0) {
				return 0;
			}

			return status_code;
		}

		/* If we are reading the message body... */
		if (backend_connection->request_state == 19) {
			continue;
		}

		data = buffer->data;
		offset = backend_connection->offset;

		while (offset < buffer->used) {
			c = (unsigned char) data[offset];
			switch (backend_connection->request_state) {
				case 0: /* Initial state. */
					if (c == '\r') {
						backend_connection->request_state = 1; /* '\r' before status line. */
					} else if ((c == 'H') || (c == 'h')) {
						backend_connection->request_state = 2; /* [H]TTP/<major>.<minor> */
					} else if (c != '\n') {
						return 400; /* Bad Request. */
					}

					break;
				case 1: /* '\r' before status line. */
					if (c == '\n') {
						backend_connection->request_state = 0; /* Initial state. */
					} else {
						return 400; /* Bad Request. */
					}

					break;
				case 2: /* [H]TTP/<major>.<minor> */
					if ((c == 'T') || (c == 't')) {
						backend_connection->request_state = 3; /* H[T]TP/<major>.<minor> */
					} else {
						return 400; /* Bad Request. */
					}

					break;
				case 3: /* H[T]TP/<major>.<minor> */
					if ((c == 'T') || (c == 't')) {
						backend_connection->request_state = 4; /* HT[T]P/<major>.<minor> */
					} else {
						return 400; /* Bad Request. */
					}

					break;
				case 4: /* HT[T]P/<major>.<minor> */
					if ((c == 'P') || (c == 'p')) {
						backend_connection->request_state = 5; /* HTT[P]/<major>.<minor> */
					} else {
						return 400; /* Bad Request. */
					}

					break;
				case 5: /* HTT[P]/<major>.<minor> */
					if (c == '/') {
						backend_connection->request_state = 6; /* HTTP[/]<major>.<minor> */
					} else {
						return 400; /* Bad Request. */
					}

					break;
				case 6: /* HTTP[/]<major>.<minor> */
					if (IS_DIGIT (c)) {
						backend_connection->request_state = 7; /* HTTP/[<major>].<minor> */
					} else {
						return 400; /* Bad Request. */
					}

					break;
				case 7: /* HTTP/[<major>].<minor> */
					if (c == '.') {
						backend_connection->request_state = 8; /* HTTP/<major>[.]<minor> */
					} else if (!IS_DIGIT (c)) {
						return 400; /* Bad Request. */
					}

					break;
				case 8: /* HTTP/<major>[.]<minor> */
					if (IS_DIGIT (c)) {
						backend_connection->request_state = 9; /* HTTP/<major>.[<minor>] */
					} else {
						return 400; /* Bad Request. */
					}

					break;
				case 9: /* HTTP/<major>.[<minor>] */
					if (IS_WHITE_SPACE (c)) {
						backend_connection->request_state = 10; /* Whitespace after version. */
					} else if (!IS_DIGIT (c)) {
						return 400; /* Bad Request. */
					}

					break;
				case 10: /* Whitespace after version. */
					if (IS_DIGIT (c)) {
						backend_connection->status_code_offset = offset;

						backend_connection->request_state = 11; /* Status code. */
					} else if (!IS_WHITE_SPACE (c)) {
						return 400; /* Bad Request. */
					}

					break;
				case 11: /* Status code. */
					if (IS_WHITE_SPACE (c)) {
						backend_connection->request_state = 12; /* Whitespace after status code. */
					} else if (c == '\r') {
						backend_connection->request_state = 13; /* '\r' */
					} else if (c == '\n') {
						backend_connection->nCRLFs++;
						backend_connection->request_state = 14; /* '\n' */
					} else if (!IS_DIGIT (c)) {
						return 400; /* Bad Request. */
					}

					break;
				case 12: /* Whitespace after status code. */
					if (c == '\r') {
						backend_connection->request_state = 13; /* '\r' */
					} else if (c == '\n') {
						backend_connection->nCRLFs++;
						backend_connection->request_state = 14; /* '\n' */
					} else if (c < ' ') {
						return 400; /* Bad Request. */
					}

					break;
				case 13: /* '\r' */
					if (c == '\n') {
						if (backend_connection->nCRLFs == 1) {
							backend_connection->header_offsets[0][1] = offset + 1;

							status_code = 200;
							backend_connection->request_state = 19; /* Reading message body. */

							break;
						}

						backend_connection->nCRLFs++;

						backend_connection->request_state = 14; /* '\n' */
					} else {
						return 400; /* Bad Request. */
					}

					break;
				case 14: /* '\n' */
					if (backend_connection->noffsets == 0) {
						backend_connection->header_offsets[0][0] = offset;
						backend_connection->noffsets++;
					}

					if (c == '\r') {
						if ((backend_connection->message_header == CONNECTION_HEADER) || ((backend_connection->message_header == SERVER_HEADER) && (!client_connection->proxy_connection))) {
							backend_connection->header_offsets[backend_connection->noffsets][1] = offset;
							backend_connection->noffsets++;
						}

						backend_connection->request_state = 13; /* '\r' */
					} else if (c == '\n') {
						if ((backend_connection->message_header == CONNECTION_HEADER) || ((backend_connection->message_header == SERVER_HEADER) && (!client_connection->proxy_connection))) {
							backend_connection->header_offsets[backend_connection->noffsets][1] = offset;
							backend_connection->noffsets++;
						}

						backend_connection->header_offsets[0][1] = offset + 1;

						status_code = 200;
						backend_connection->request_state = 19; /* Reading message body. */
					} else if (IS_WHITE_SPACE (c)) {
						backend_connection->nCRLFs = 0;
						backend_connection->request_state = 15; /* Multiple lines value. */
					} else if (c == ':') {
						return 400; /* Bad Request. */
					} else if ((c > ' ') && (c < 127)) {
						if ((backend_connection->message_header == CONNECTION_HEADER) || ((backend_connection->message_header == SERVER_HEADER) && (!client_connection->proxy_connection))) {
							backend_connection->header_offsets[backend_connection->noffsets][1] = offset;
							backend_connection->noffsets++;
						}

						/* RFC 822
						 * 3.1.2. STRUCTURE OF HEADER FIELDS
						 * The field-name must be composed of printable ASCII characters
						 * (i.e., characters that have values between 33. and 126.,
						 * decimal, except colon). */
						backend_connection->token = offset;

						backend_connection->nCRLFs = 0;

						backend_connection->request_state = 16; /* Header field. */
					} else {
						return 400; /* Bad Request. */
					}

					break;
				case 15: /* Multiple lines value. */
					if (c == '\r') {
						backend_connection->request_state = 13; /* '\r' */
					} else if (c == '\n') {
						backend_connection->nCRLFs++;
						backend_connection->request_state = 14; /* '\n' */
					} else if (c > ' ') {
						if ((backend_connection->message_header == CONNECTION_HEADER) || (backend_connection->message_header == CONTENT_LENGTH_HEADER)) {
							if (backend_connection->token) {
								return 400; /* Bad Request. */
							}
						}

						backend_connection->token = offset;
						backend_connection->end = 0;

						backend_connection->request_state = 18; /* Header value. */
					} else if (!IS_WHITE_SPACE (c)) {
						return 400; /* Bad Request. */
					}

					break;
				case 16: /* Header field. */
					if (c == ':') {
						/* Get header field. */
						backend_connection->message_header = get_header (data + backend_connection->token, offset - backend_connection->token);

						if ((backend_connection->message_header == CONNECTION_HEADER) || ((backend_connection->message_header == SERVER_HEADER) && (!client_connection->proxy_connection))) {
							if (backend_connection->noffsets == sizeof (backend_connection->header_offsets) / sizeof (off_t) / 2) {
								return 400; /* Bad Request. */
							}

							backend_connection->header_offsets[backend_connection->noffsets][0] = backend_connection->token;
						}

						backend_connection->token = 0;

						backend_connection->request_state = 17; /* ':' after header field. */
					} else if (c <= ' ') {
						return 400; /* Bad Request. */
					}

					break;
				case 17: /* ':' after header field. */
					if (c == '\r') {
						backend_connection->request_state = 13; /* '\r' */
					} else if (c == '\n') {
						backend_connection->nCRLFs++;
						backend_connection->request_state = 14; /* '\n' */
					} else if (c > ' ') {
						backend_connection->token = offset;
						backend_connection->end = 0;

						backend_connection->request_state = 18; /* Header value. */
					} else if (!IS_WHITE_SPACE (c)) {
						return 400; /* Bad Request. */
					}

					break;
				case 18: /* Header value. */
					if ((c == '\r') || (c == '\n')) {
						if (backend_connection->message_header == CONTENT_LENGTH_HEADER) {
							if (backend_connection->end) {
								len = backend_connection->end - backend_connection->token;
							} else {
								len = offset - backend_connection->token;
							}

							content_length = 0;

							ptr = data + backend_connection->token;
							end = ptr + len;
							while (ptr < end) {
								if ((*ptr < '0') || (*ptr > '9')) {
									return 400; /* Bad Request. */
								}

								content_length = (content_length * 10) + (*ptr - '0');
								ptr++;
							}

							backend_connection->content_length = content_length;
						} else if (backend_connection->message_header == TRANSFER_ENCODING_HEADER) {
							if (backend_connection->content_length == -1) {
								if (backend_connection->end) {
									len = backend_connection->end - backend_connection->token;
								} else {
									len = offset - backend_connection->token;
								}

								if ((len == sizeof ("chunked") - 1) && (strncasecmp (data + backend_connection->token, "chunked", len) == 0)) {
									backend_connection->content_length = 0;
								}
							}
						}

						if (c == '\r') {
							backend_connection->request_state = 13; /* '\r' */
						} else {
							backend_connection->nCRLFs++;
							backend_connection->request_state = 14; /* '\n' */
						}
					} else if (IS_WHITE_SPACE (c)) {
						if (!backend_connection->end) {
							backend_connection->end = offset;
						}
					} else if (c > ' ') {
						/* If a new word starts... */
						if (backend_connection->end) {
							if ((backend_connection->message_header == CONNECTION_HEADER) || (backend_connection->message_header == CONTENT_LENGTH_HEADER)) {
								return 400; /* Bad Request. */
							}

							backend_connection->end = 0;
						}
					} else {
						return 400; /* Bad Request. */
					}

					break;
			}

			offset++;
		}

		backend_connection->offset = offset;
	} while (1);
}

int prepare_request_to_backend (http_connection_t *backend_connection, http_connection_t *client_connection)
{
	backend_list_t *backend_list;
	backend_t *backend;
	buffer_t *buffer;
	char *data;
	char client[20];
	unsigned char c;
	size_t diff;
	size_t i;
	int n;

	buffer = &backend_connection->buffer;
	if (buffer_allocate (buffer, client_connection->buffer.used + (client_connection->path.used * 2)) < 0) {
		return -1;
	}

	if (buffer_format (buffer, "%.*s ", methods[client_connection->http_method].len, methods[client_connection->http_method].string) < 0) {
		return -1;
	}

	if (!inet_ntop (AF_INET, &(client_connection->sin.sin_addr), client, sizeof (client))) {
		return -1;
	}

	/* Escape URI. */
	data = client_connection->path.data;
	for (i = 0; ((c = (unsigned char) data[i]) != 0); i++) {
		if ((IS_ALPHA (c)) || (IS_DIGIT (c)) || (IS_MARK (c))) {
			buffer->data[buffer->used++] = c;
		} else if (c == '/') {
			buffer->data[buffer->used++] = '/';
		} else if ((c == '?') || (c == '#')) {
			n = client_connection->path.used - 1 - i;
			memcpy (buffer->data + buffer->used, data + i, n);
			buffer->used += n;

			break;
		} else {
			buffer->data[buffer->used++] = '%';

			n = c / 16;
			if (n < 10) {
				buffer->data[buffer->used++] = '0' + n;
			} else {
				buffer->data[buffer->used++] = 'A' + n - 10;
			}

			n = c % 16;
			if (n < 10) {
				buffer->data[buffer->used++] = '0' + n;
			} else {
				buffer->data[buffer->used++] = 'A' + n - 10;
			}
		}
	}

	if (!client_connection->proxy_connection) {
		backend_list = (backend_list_t *) client_connection->backend_list;
		backend = &(backend_list->backends[client_connection->backend]);

		if (buffer_format (buffer, " HTTP/%d.%d\r\nHost: %.*s\r\nConnection: close\r\nX-Real-IP: %s\r\n", client_connection->major_number, client_connection->minor_number, backend->host.used, backend->host.data, client) < 0) {
			return -1;
		}
	} else {
		if (buffer_format (buffer, " HTTP/%d.%d\r\nHost: %.*s\r\nConnection: close\r\nX-Real-IP: %s\r\n", client_connection->major_number, client_connection->minor_number, client_connection->host.used, client_connection->host.data, client) < 0) {
			return -1;
		}
	}

	data = client_connection->buffer.data;

	if (client_connection->noffsets == 1) {
		if (buffer_append_size_bounded_string (buffer, data + client_connection->header_offsets[0][0], client_connection->header_offsets[0][1] - client_connection->header_offsets[0][0]) < 0) {
			return -1;
		}
	} else {
		for (i = 1; i < client_connection->noffsets; i++) {
			diff = client_connection->header_offsets[i][0] - client_connection->header_offsets[i - 1][((i == 1)?0:1)];
			if (diff > 0) {
				if (buffer_append_size_bounded_string (buffer, data + client_connection->header_offsets[i - 1][((i == 1)?0:1)], diff) < 0) {
					return -1;
				}
			}
		}

		if (buffer_append_size_bounded_string (buffer, data + client_connection->header_offsets[client_connection->noffsets - 1][1], client_connection->header_offsets[0][1] - client_connection->header_offsets[client_connection->noffsets - 1][1]) < 0) {
			return -1;
		}
	}

	if (client_connection->buffer.used > client_connection->header_offsets[0][1]) {
		if (buffer_append_size_bounded_string (buffer, data + client_connection->header_offsets[0][1], client_connection->buffer.used - client_connection->header_offsets[0][1]) < 0) {
			return -1;
		}
	}

	return 0;
}

int prepare_response_to_client (http_connection_t *backend_connection, http_connection_t *client_connection)
{
	buffer_t *buffer;
	char *data;
	size_t diff;
	size_t i;

	buffer = &client_connection->buffer;
	buffer_reset (buffer);
	if (buffer_allocate (buffer, backend_connection->buffer.used) < 0) {
		return -1;
	}

	data = backend_connection->buffer.data;

	/* Add status-line. */
	if (buffer_append_size_bounded_string (buffer, "HTTP/1.1 ", 9) < 0) {
		return -1;
	}

	if (buffer_append_size_bounded_string (buffer, data + backend_connection->status_code_offset, backend_connection->header_offsets[0][0] - backend_connection->status_code_offset) < 0) {
		return -1;
	}

	if (!client_connection->proxy_connection) {
#define SERVER_HEADER "Server: %s\r\n"
		if (buffer_format (buffer, SERVER_HEADER, WEBSERVER_NAME) < 0) {
			return -1;
		}
	}

	if (!client_connection->keep_alive) {
#define CONNECTION_CLOSE "Connection: close\r\n"
		if (buffer_append_size_bounded_string (buffer, CONNECTION_CLOSE, sizeof (CONNECTION_CLOSE) - 1) < 0) {
			return -1;
		}
	} else {
#define CONNECTION_KEEP_ALIVE "Connection: Keep-Alive\r\n"
		if (buffer_append_size_bounded_string (buffer, CONNECTION_KEEP_ALIVE, sizeof (CONNECTION_KEEP_ALIVE) - 1) < 0) {
			return -1;
		}
	}

	if (backend_connection->content_length == -1) {
#define TRANSFER_ENCODING_CHUNKED "Transfer-Encoding: chunked\r\n"
		if (buffer_append_size_bounded_string (buffer, TRANSFER_ENCODING_CHUNKED, sizeof (TRANSFER_ENCODING_CHUNKED) - 1) < 0) {
			return -1;
		}
	}

	if (backend_connection->noffsets == 1) {
		if (buffer_append_size_bounded_string (buffer, data + backend_connection->header_offsets[0][0], backend_connection->header_offsets[0][1] - backend_connection->header_offsets[0][0]) < 0) {
			return -1;
		}
	} else {
		for (i = 1; i < backend_connection->noffsets; i++) {
			diff = backend_connection->header_offsets[i][0] - backend_connection->header_offsets[i - 1][((i == 1)?0:1)];
			if (diff > 0) {
				if (buffer_append_size_bounded_string (buffer, data + backend_connection->header_offsets[i - 1][((i == 1)?0:1)], diff) < 0) {
					return -1;
				}
			}
		}

		if (buffer_append_size_bounded_string (buffer, data + backend_connection->header_offsets[backend_connection->noffsets - 1][1], backend_connection->header_offsets[0][1] - backend_connection->header_offsets[backend_connection->noffsets - 1][1]) < 0) {
			return -1;
		}
	}

	if (client_connection->http_method != HEAD_METHOD) {
		diff = backend_connection->buffer.used - backend_connection->header_offsets[0][1];
		if (diff > 0) {
			if (backend_connection->content_length == -1) {
				if (buffer_format (buffer, "%x\r\n", diff) < 0) {
					return -1;
				}
			}

			if (buffer_append_size_bounded_string (buffer, data + backend_connection->header_offsets[0][1], diff) < 0) {
				return -1;
			}
		}
	}

	return 0;
}
